package kubernetes

import (
	"crypto/tls"
	"fmt"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"regexp"
	"strings"
	"time"

	"github.com/golang/glog"

	"eccgateway/pkg/config"
	"eccgateway/pkg/util"
)

var kubernetesAddress string

type KubernetesController struct{}

var ssl *tls.Config

const (
	HttpsProtocol          = "https"
	OriginHostReqHeader    = "X-Origin-Host"
	ForwardedHostReqHeader = "X-Forwarded-Host"

	kubernetesPrefix = "/kubernetes"

	kubernetesOriginPrefix = "/k8s-origin"
)

func init() {
	if len(kubernetesAddress) == 0 {
		kubernetesAddress = strings.TrimSuffix(config.GetConfig().String("kubernetesAddress"), "/")
		glog.Infof("kubernetesAddress is %v", kubernetesAddress)
	}

	kubernetesCertFile := config.GetConfig().String("kubernetesCert")
	kubernetesKeyFile := config.GetConfig().String("kubernetesKey")
	if ssl == nil && (kubernetesCertFile != "" || kubernetesKeyFile != "") {
			glog.Infof("kubernetesCertFile: %v, kubernetesKeyFile: %v", kubernetesCertFile, kubernetesKeyFile)
		cert, err := tls.LoadX509KeyPair(kubernetesCertFile, kubernetesKeyFile)
		if err != nil {
			glog.Errorf("initialize kubernetes certificate failed, err: %v", err)
			panic(err)
		}

		ssl = &tls.Config{
			Certificates:       []tls.Certificate{cert},
			InsecureSkipVerify: true,
		}
		glog.Infof("the kubernetes certificate is initialized")
	}
}

func (c *KubernetesController) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()

	var trueUri string
	if strings.HasPrefix(r.RequestURI, kubernetesPrefix) {
		trueUri = strings.TrimPrefix(r.RequestURI, kubernetesPrefix)
	} else {
		trueUri = strings.TrimPrefix(r.RequestURI, kubernetesOriginPrefix)
	}

	reg := regexp.MustCompile(`(.*)\?.*`)
	trueUri = reg.ReplaceAllString(trueUri, "${1}")

	origin, err := url.Parse(kubernetesAddress)
	if err != nil {
		glog.Errorf("parse proxy backend address failed, address: %v, err: %v", kubernetesAddress, err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, fmt.Sprintf("parse proxy backend address failed, %v", err))
		return
	}

	glog.Infof("this request will send to address: %v, uri: %v, method: %v", kubernetesAddress, trueUri, r.Method)
	proxy, err := getKubernetesProxy(origin, trueUri)
	if err != nil {
		glog.Errorf("initialize kubernetes certificate failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, fmt.Sprintf("initialize kubernetes certificate failed, %v", err))
		return
	}
	proxy.ServeHTTP(w, r)
}

func getKubernetesProxy(origin *url.URL, trueUri string) (*httputil.ReverseProxy, error) {
	director := func(req *http.Request) {
		req.Header.Add(ForwardedHostReqHeader, req.Host)
		req.Header.Add(OriginHostReqHeader, origin.Host)
		req.URL.Scheme = HttpsProtocol
		req.URL.Host = origin.Host
		req.URL.Path = trueUri
	}
	return &httputil.ReverseProxy{
		Director: director,
		Transport: &http.Transport{
			TLSClientConfig: ssl,
			Proxy:           http.ProxyFromEnvironment,
			DialContext: (&net.Dialer{
				Timeout:   30 * time.Second,
				KeepAlive: 30 * time.Second,
				DualStack: true,
			}).DialContext,
			ForceAttemptHTTP2:     true,
			MaxIdleConns:          100,
			IdleConnTimeout:       90 * time.Second,
			TLSHandshakeTimeout:   10 * time.Second,
			ExpectContinueTimeout: 1 * time.Second,
		}}, nil
}
